package com.bage.finance.biz.service.impl;

import com.bage.common.dto.TokenResponse;
import com.bage.common.exception.BizException;
import com.bage.common.exception.ParameterException;
import com.bage.common.service.TokenService;
import com.bage.finance.biz.config.ObjectConvertor;
import com.bage.finance.biz.constant.RedisKeyConstant;
import com.bage.finance.biz.domain.MemberBindPhone;
import com.bage.finance.biz.domain.MemberBindWxOpenId;
import com.bage.finance.biz.dto.AdminDTO;
import com.bage.finance.biz.dto.form.PhoneRegisterForm;
import com.bage.finance.biz.dto.vo.GenerateMpRegCodeVo;
import com.bage.finance.biz.enums.SmsCodeTypeEnum;
import com.bage.finance.biz.service.*;
import com.bage.wx.config.WxConfig;
import com.bage.wx.dto.AccessTokenResult;
import com.bage.wx.dto.MpQrCodeCreateRequest;
import com.bage.wx.dto.MpQrCodeCreateResult;
import com.bage.wx.dto.MpSubscribeEventRequest;
import com.bage.wx.service.WXService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class MemberRegServiceImpl implements MemberRegService {

    final MemberLoginService memberLoginService;

    final RedissonClient redissonClient;

    final TransactionTemplate transactionTemplate;

    final TenantService tenantService;

    final MemberService memberService;

    final MemberBindPhoneService memberBindPhoneService;

    final WXService wxService;

    final WxConfig wxConfig;

    final ObjectConvertor objectConvertor;

    final RedisTemplate<String, Object> redisTemplate;

    final MemberBindWxOpenIdService memberBindWxOpenIdService;

    final TokenService<AdminDTO> adminTokenService;

    @EventListener
    public void handleMpSubscribeEventRequest(MpSubscribeEventRequest mpSubscribeEventRequest) {
        log.info("接收到消息: MpSubscribeEventRequest: {}", mpSubscribeEventRequest.toString());
        log.info("0: {}", mpSubscribeEventRequest.getEvent());
        // 订阅事件 -- 未关注
        if ("subscribe".equals(mpSubscribeEventRequest.getEvent()) && Strings.isNotBlank(mpSubscribeEventRequest.getEventKey())) {
            String[] keys = mpSubscribeEventRequest.getEventKey().split("_");
            if ("qrscene".equals(keys[0]) && "ScanReg".equals(keys[1])) {
                log.info("AppId: {}, ClientId: {}", keys[1], keys[3]);
                registerByMpOpenId(keys[2], keys[3], mpSubscribeEventRequest.getToUserName());
                return;
            }
        }

        // 扫描事件 -- 已关注
        if ("SCAN".equals(mpSubscribeEventRequest.getEvent()) && Strings.isNotBlank(mpSubscribeEventRequest.getEventKey())) {
            String[] keys = mpSubscribeEventRequest.getEventKey().split("_");
            if ("ScanReg".equals(keys[0])) {
                log.info("AppId: {}, ClientId: {}", keys[1], keys[2]);
                registerByMpOpenId(keys[1], keys[2], mpSubscribeEventRequest.getToUserName());
                return;
            }
        }
    }

    private TokenResponse registerByMpOpenId(String appId, String clientId, String openId) {
        // 扫描注册
        long memberId = scReg(appId, openId);
        AdminDTO adminDTO = new AdminDTO();
        adminDTO.setId(memberId);
        adminTokenService.setToken(adminDTO);
        redisTemplate.opsForValue().set(RedisKeyConstant.CLIENT_TOKEN_KEY + clientId, adminDTO.getToken(), 10, TimeUnit.MINUTES);
        return adminDTO.getToken();
    }

    private long scReg(String appId, String openId) {
        MemberBindWxOpenId memberBindWxOpenId = memberBindWxOpenIdService.get(appId, openId);
        if (Objects.nonNull(memberBindWxOpenId)) {
            return memberBindWxOpenId.getMemberId();
        }

        // 将游客数据入口(保证数据一致性)
        Long memberId = transactionTemplate.execute(transactionStatus -> {
            // 创建租户id
            long tenantId = tenantService.add();
            long id = memberService.reg(tenantId);
            memberBindWxOpenIdService.reg(appId, openId, id);
            return id;
        });
        if (memberId == null) {
            throw new BizException("注册失败");
        }
        return memberId;
    }

    @Override
    public Long phoneReg(PhoneRegisterForm form) {
        if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
            throw new ParameterException("两次输入的密码不一致");
        }
        memberLoginService.checkSmsCode(form.getPhone(), form.getSmsCode(), SmsCodeTypeEnum.REG.getCode());

        // redis分布式锁
        RLock rLock = redissonClient.getLock("PHONE_REG:" + form.getPhone());
        try {
            // 相同的手机号会阻塞 -- 防止相同的手机号重复注册
            rLock.lock();
            MemberBindPhone memberBindPhone = memberBindPhoneService.getMemberByPhone(form.getPhone());
            if (memberBindPhone != null) {
                log.warn("手机号: {} 已注册", form.getPhone());
                throw new BizException("手机号已注册");
            }
            // 将游客数据入口(保证数据一致性)
            Long memberId = transactionTemplate.execute(transactionStatus -> {
                long tenantId = tenantService.add();
                long id = memberService.reg(tenantId);
                if (id <= 0) {
                    throw new BizException("注册异常");
                }
                memberBindPhoneService.reg(form.getPhone(), id, form.getPassword());
                return id;
            });
            if (memberId == null) {
                throw new BizException("注册失败");
            }
            return memberId;
        } catch (Exception ex) {
            throw new BizException("注册失败", ex);
        } finally {
            rLock.unlock();
        }
    }

    @Override
    public GenerateMpRegCodeVo generateMpRegCode(String clientId) {
        // 直接调用微信接口获取二维码
        // AccessTokenResult accessTokenResult = wxService.getMpAccessToken(wxConfig.getMp().getAppId(), wxConfig.getMp().getSecret());

        // 从缓存中获取二维码
        AccessTokenResult accessTokenResult = wxService.getMpAccessTokenByCache(wxConfig.getMp().getAppId());
        MpQrCodeCreateRequest request = new MpQrCodeCreateRequest();
        request.setExpireSeconds(wxConfig.getMp().getCodeExpire());
        request.setActionName("QR_STR_SCENE");
        request.setActionInfo(request.new ActionInfo());
        request.getActionInfo().setScene(request.new scene());
        request.getActionInfo().getScene().setSceneStr("ScanReg_" + wxConfig.getMp().getAppId() + "_" + clientId);
        MpQrCodeCreateResult result = wxService.createMpQrcodeCreate(accessTokenResult.getAccessToken(), request);

        // 对象转换
        return objectConvertor.toGenerateMpRegCodeResponse(result);
    }
}
